/*LICENSE_BEGIN
 * ******************************************************************
 * All original parts of SoccerScope2003 have copyright (2003) by
 * Team YowAI, represented by Mr. Shuhei Shiota
 * The Department of Creative Informatics
 * The Graduate School of Information Science and Technology
 * The University of Tokyo
 * 
 * Additional classes (except libraries in /lib) were developed by 
 * the Dainamite Robocup-Team of the DAI-Labor, TU-Berlin 
 * (www.dai-labor.de)
 * 
 * Extended/new classes/packages are:
 *   soccerscope.analyzer.LogfileAnalyzer
 *   soccerscope.daistatistic.*
 *   soccerscope.daistatistic.charts.*
 *   soccerscope.daistatistic.tools.*
 *   soccerscope.db.*
 *   soccerscope.dialog.*
 *   soccerscope.model.PlayerWorldModel
 *   soccerscope.model.SimplePlayer
 *   soccerscope.net.AgentConnector
 *   soccerscope.training.*
 *   soccerscope.training.conditions.*
 * ******************************************************************
LICENSE_END*/
package soccerscope.model;

import java.util.LinkedList;

import soccerscope.util.geom.Geometry;
import soccerscope.util.geom.Line2f;
import soccerscope.util.geom.Point2f;
import soccerscope.util.geom.Vector2f;

public class Player extends SoccerObject {
    // player mode
    public final static int DISABLE = 0x0000;

    public final static int STAND = 0x0001;

    public final static int KICK = 0x0002;

    public final static int KICK_FAULT = 0x0004;

    public final static int GOALIE = 0x0008;

    public final static int CATCH = 0x0010;

    public final static int CATCH_FAULT = 0x0020;

    public final static int BALL_TO_PLAYER = 0x0040;

    public final static int PLAYER_TO_BALL = 0x0080;

    public final static int DISCARD = 0x0100;

    public final static int LOST = 0x0200;

    public final static int BALL_COLLIDE = 0x0400;

    public final static int PLAYER_COLLIDE = 0x0800;

    public final static int TACKLE = 0x1000;

    public final static int TACKLE_FAULT = 0x2000;

    public final static int BACK_PASS = 0x4000;

    public final static int FREE_KICK_FAULT = 0x8000;

    // player type
    public final static int PT_NULL = 0;

    public final static int PT_DEFAULT = 0;

    public final static int PT_HETRO_A = 1;

    public final static int PT_HETRO_B = 2;

    public final static int PT_HETRO_C = 3;

    public final static int PT_HETRO_D = 4;

    public final static int PT_HETRO_E = 5;

    public final static int PT_HETRO_F = 6;

    // view quality
    public final static int LOW = 0;

    public final static int HIGH = 1;

    // error direction
    public final static int ERROR_DIR = 1024;

    /** The WorldModel of the player if he has transmit this data. */
    private PlayerWorldModel worldModel;

    public boolean reliable = true;

    public int side;

    public int unum;

    public int stamina;

    public float angle; // degree

    public int angleNeck; // degree

    public int angleVisible; // degree

    public int viewQuality;

    public String sayMessage;

    public LinkedList<String> hearMessage;

    public float catchDir;

    public boolean offside;

    public int mode;

    public int type;

    public int playerType;

    public int interceptionTime;

    public int roleNumber = -1;

    /*
     * public double effort; public double recovery;
     */
    public int kickCount;

    public int dashCount;

    public int turnCount;

    public int sayCount;

    public int turnNeckCount;

    public int catchCount;

    public int moveCount;

    public int changeViewCount;

    public String state = "";

    public Player(int teamSide) {
        super();
        this.side = teamSide;
        this.unum = 0;
        this.stamina = 0;
        this.angle = 0;
        this.angleNeck = 0;
        this.angleVisible = 90;
        this.viewQuality = HIGH;
        this.sayMessage = "";
        this.hearMessage = new LinkedList<String>();
        this.catchDir = ERROR_DIR;
        this.offside = false;
        this.mode = DISABLE;
        this.type = PT_DEFAULT;
        this.playerType = 0;

        /*
         * effort = 1.0; recovery = 1.0;
         */
        this.kickCount = 0;
        this.dashCount = 0;
        this.sayCount = 0;
        this.turnNeckCount = 0;
        this.catchCount = 0;
        this.changeViewCount = 0;

    }

    public void setPlayer(Player p) {
        this.pos.set(p.pos);
        this.vel.set(p.vel);
        this.acc.set(p.acc);
        this.side = p.side;
        this.unum = p.unum;
        this.stamina = p.stamina;
        this.angle = p.angle;
        this.angleNeck = p.angleNeck;
        this.angleVisible = p.angleVisible;
        this.viewQuality = p.viewQuality;
        this.sayMessage = p.sayMessage;
        this.hearMessage = p.hearMessage;
        this.catchDir = p.catchDir;
        this.offside = p.offside;
        this.mode = p.mode;
        this.type = p.type;
        this.playerType = p.playerType;
        this.roleNumber = p.roleNumber;
    }

    public boolean isEnable() {
        return ((this.mode & STAND) == STAND);
    }

    public boolean isGoalie() {
        return ((this.mode & GOALIE) == GOALIE);
    }

    public boolean isKicking() {
        return ((this.mode & KICK) == KICK);
    }

    public boolean isKickFault() {
        return ((this.mode & KICK_FAULT) == KICK_FAULT);
    }

    public boolean isCatching() {
        return ((this.mode & CATCH) == CATCH);
    }

    public boolean isCatchFault() {
        return ((this.mode & CATCH_FAULT) == CATCH_FAULT);
    }

    public boolean isCollision() {
        return ((this.mode & BALL_COLLIDE) == BALL_COLLIDE)
                || ((this.mode & PLAYER_COLLIDE) == PLAYER_COLLIDE);
    }

    public boolean isTackling() {
        return ((this.mode & TACKLE) == TACKLE);
    }

    public boolean isTackleFault() {
        return ((this.mode & TACKLE_FAULT) == TACKLE_FAULT);
    }

    @Override
    public float getSize() {
        return HeteroParam.get(this.type).player_size;
    }

    @Override
    public java.awt.Color getColor() {
        switch (this.side) {
        case Team.LEFT_SIDE:
            if (isGoalie())
                return ColorDB.getColor("goalie_l_color");
            return ColorDB.getColor("team_l_color");
        case Team.NEUTRAL:
            if (isGoalie())
                return ColorDB.getColor("nuetral_goalie");
            return ColorDB.getColor("nuetral_player");
        case Team.RIGHT_SIDE:
            if (isGoalie())
                return ColorDB.getColor("goalie_r_color");
            return ColorDB.getColor("team_r_color");
        }
        return java.awt.Color.white;
    }

    public boolean isOffside(float line) {
        return this.side == Team.LEFT_SIDE && this.pos.x > line
                || this.side == Team.RIGHT_SIDE && this.pos.x < line;
    }

    public boolean isKickable(Point2f ball) {
        return this.pos.distance(ball) <= (Param.BALL_SIZE + getSize() + HeteroParam
                .get(this.type).kickable_margin);
    }

    public boolean isCatchable(Point2f ball) {
        return this.pos.distance(ball) <= Param.GOALIE_CATCHABLE_AREA_LENGTH;
    }

    public boolean canTackle(Point2f ball) {
        Vector2f rb = toRelative(ball);
        return (rb.x >= -Param.TACKLE_BACK_DIST && rb.x <= Param.TACKLE_DIST && Math
                .abs(rb.y) <= Param.TACKLE_WIDTH / 2);
    }

    public void step() {
        this.pos.add(this.vel);
        this.pos.add(this.acc);
        this.vel.add(this.acc);
        this.vel.scale(HeteroParam.get(this.type).player_decay);
        this.acc.set(0, 0);
    }

    public float getKickable() {
        return (Param.BALL_SIZE + getSize() + HeteroParam.get(this.type).kickable_margin);
    }

    public float getMoveMax() {
        return (1 + Param.PLAYER_RAND) * HeteroParam.get(this.type).player_speed_max;
    }

    public float getTurnAngle() {
        return 180 / (1 + HeteroParam.get(this.type).inertia_moment * this.vel.r());
    }

    public float getDashAccelerationMax() {
        return HeteroParam.get(this.type).dash_power_rate
                * HeteroParam.get(this.type).effort_max * Param.POWER_MAX;
    }

    public Vector2f getVector() {
        return Vector2f.polar2vector(getSize(), this.angle);
    }

    public Line2f getLine() {
        Point2f t = new Point2f(this.pos);
        t.add(getVector());
        return new Line2f(this.pos, t);
    }

    public Vector2f toRelative(Point2f p) {
        return new Vector2f(this.pos, p).rotate(-this.angle);
    }

    public float getAngleFromBody(Point2f p) {
        return Geometry.normalizeAngle(this.pos.dir(p) - this.angle);
    }

    public float getAngleFromNeck(Point2f p) {
        return Geometry.normalizeAngle(this.pos.dir(p) - this.angle - this.angleNeck);
    }

    public Vector2f getDashAccelerate(float power) {
        // effort == 1 �Ȥ��Ʒ׻�
        return Vector2f.polar2vector(HeteroParam.get(this.type).dash_power_rate * power,
                this.angle);
    }

    public Vector2f getKickAccelerate(Point2f ball, float power, float dir) {
        Vector2f diff = new Vector2f();
        diff.sub(ball, this.pos);
        float effectiveKickPowerRate = Param.KICK_POWER_RATE;
        effectiveKickPowerRate *= 1.0 - (diff.length() - Param.BALL_SIZE - getSize())
                / HeteroParam.get(this.type).kickable_margin * 0.25
                - Math.abs(Geometry.normalizeAngle(diff.th() - this.angle)) / 180.0
                * 0.25;
        return Vector2f.polar2vector(effectiveKickPowerRate * power, Geometry
                .normalizeAngle(this.angle + dir));
    }

    public Vector2f getFinalMove() {
        return Vector2f.polar2vector(calcFinalDistance(), this.vel.th());
    }

    public float getTackleProb(Point2f p) {
        Vector2f tmp = toRelative(p);
        float prob = (float) (1.0 - (Math.pow((tmp.x > 0) ? tmp.x / Param.TACKLE_DIST
                : -tmp.x / Param.TACKLE_BACK_DIST, Param.TACKLE_EXPONENT) + Math.pow(Math
                .abs(tmp.y)
                / (Param.TACKLE_WIDTH / 2.0f), Param.TACKLE_EXPONENT)));
        return prob;
    }

    // (a == 0)(t >= 0)
    public float calcVelocityAfterNTime(int t) {
        float v = this.vel.r();
        float d = HeteroParam.get(this.type).player_decay;
        return v * (float) Math.pow(d, t);
    }

    // (a == 0)(t >= 0)
    public float calcDistanceAfterNTime(int t) {
        float v = this.vel.r();
        float d = HeteroParam.get(this.type).player_decay;
        return v * (1 - (float) Math.pow(d, t)) / (1 - d);
    }

    // (a != 0)(t >= 0)
    public float calcVelocityAfterNTime(float a, int t) {
        float d = HeteroParam.get(this.type).player_decay;
        return calcVelocityAfterNTime(t) + a
                * ((1 - (float) Math.pow(d, t + 1)) / (1 - d) - 1);
    }

    // (a != 0)(t >= 0)
    public float calcDistanceAfterNTime(float a, int t) {
        float d = HeteroParam.get(this.type).player_decay;
        return calcDistanceAfterNTime(t) + (a / (1 - d))
                * (t + 1 - (1 - (float) Math.pow(d, t + 1)) / (1 - d));
    }

    // (t -> ��)(a != 0)
    public float calcFinalVelocity(float a) {
        float d = HeteroParam.get(this.type).player_decay;
        return a * d / (1 - d);
    }

    // (t -> ��)(a == 0)
    public float calcFinalDistance() {
        float v = this.vel.r();
        float d = HeteroParam.get(this.type).player_decay;
        return v / (1 - d);
    }

    // (a == 0)(vt < v)
    public int calcTimeByVelocity(float vt) {
        // float v = this.vel.r();
        float d = HeteroParam.get(this.type).player_decay;
        return (int) Math.ceil(((float) Math.log(vt) / (float) Math.log(d))
                - ((float) Math.log(this.vel.r()) / (float) Math.log(d)));
    }

    // (a == 0)(dist >= 0)
    public int calcTimeByDistance(float dist) {
        float v = this.vel.r();
        float d = HeteroParam.get(this.type).player_decay;
        return (int) Math.ceil(((float) Math.log(1 - dist * (1 - d) / v))
                / ((float) Math.log(d)));
    }

    // (a != 0)(max(a, v) < vt <= speed_max)
    public int calcTimeByVelocity(float vt, float a) {
        float v = this.vel.r();
        float d = HeteroParam.get(this.type).player_decay;
        return (int) Math.ceil(((float) Math.log((vt - a / (1 - d) + a)
                / (v / d - a / (1 - d))) / (float) Math.log(d)) - 1);
    }

    // (a != 0)(dist >= 0)
    public int calcTimeByDistance(float dist, float a) {
        int time = (int) dist;
        float tmp_dist = calcDistanceAfterNTime(a, time);
        if (tmp_dist == dist)
            return time;
        else if (tmp_dist < dist) {
            while (tmp_dist < dist) {
                time++;
                tmp_dist = calcDistanceAfterNTime(a, time);
            }
        } else {
            while (tmp_dist > dist) {
                time--;
                tmp_dist = calcDistanceAfterNTime(a, time);
            }
            time++;
        }
        return time;
    }

    // (a != 0)(dist >= 0)
    public int estimateMinTimeByDistance(float dist, float a) {
        float d = HeteroParam.get(this.type).player_decay;
        return (int) Math.ceil(dist / (a + (a * d) / (1 - d)));
    }

    public static void main(String arg[]) {
        Player p = new Player(Team.NEUTRAL);
        float acc = p.getDashAccelerationMax();

        long time, diff;

        time = System.currentTimeMillis();
        for (int i = 0; i < 22; i++)
            for (float d = 0.0f; d < 100; d += 0.5f) {
                p.calcTimeByDistance(d, acc);
            }
        diff = System.currentTimeMillis();
        System.out.println("normal: " + (diff - time));

        time = diff;
        float table[] = new float[120];
        for (int i = 0; i < 120; i++)
            table[i] = p.calcDistanceAfterNTime(acc, i);
        diff = System.currentTimeMillis();
        System.out.println("make: " + (diff - time));

        time = diff;
        for (int i = 0; i < 22; i++)
            for (float d = 0.0f; d < 100; d += 0.5f) {
                p.search(table, d);
            }
        diff = System.currentTimeMillis();
        System.out.println("cache: " + (diff - time));
    }

    public int search(float[] t, float key) {
        int idx = (int) key;
        float tmp = t[idx];
        if (tmp == key)
            return idx;
        else if (tmp < key) {
            while (tmp < key) {
                idx++;
                tmp = t[idx];
            }
        } else {
            while (tmp > key) {
                idx--;
                tmp = t[idx];
            }
            idx++;
        }
        return idx;
    }

    @Override
    public String toString() {
        return "Player{" + "side=" + this.side + ", unum=" + this.unum + ", stamina="
                + this.stamina + ", angle=" + this.angle + ", angleNeck="
                + this.angleNeck + ", angleVisible=" + this.angleVisible
                + ", viewQuality=" + this.viewQuality + ", sayMessage='"
                + this.sayMessage + "'" + ", catchDir=" + this.catchDir + ", offside="
                + this.offside + ", mode=" + this.mode + ", type=" + this.type
                + ", kickCount=" + this.kickCount + ", dashCount=" + this.dashCount
                + ", turnCount=" + this.turnCount + ", sayCount=" + this.sayCount
                + ", turnNeckCount=" + this.turnNeckCount + ", catchCount="
                + this.catchCount + ", moveCount=" + this.moveCount
                + ", changeViewCount=" + this.changeViewCount + "}";
    }

    @Override
    public void setPosition(Point2f pos) {
        super.setPosition(pos);
    }

    /**
     * Return the WorldModel of the player.
     * @return the WorldModel or null if the player has not transmit it
     */
    public PlayerWorldModel getWorldModel() {
        return worldModel;
    }

    /**
     * Sets the WorldModel of the player.
     * @param worldModel the WorldModel of the player he has transmit
     */
    public void setWorldModel(PlayerWorldModel worldModel) {
        this.worldModel = worldModel;
    }

}
